using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

namespace Weever.Json.Binding
{
    class JsonWriter
    {
        TextWriter _writer;
        string     _separator;

        private static readonly Regex EscapeRegex =
            new Regex(@"(?<toEscape>[^\x20-\x21\x23-\x5B\x5D-\u10FFFF])", RegexOptions.Compiled);

        private const string JsonNull  = "null";
        private const string JsonTrue  = "true";
        private const string JsonFalse = "false";
        
        public JsonWriter(TextWriter writer)
        {
            _writer = writer;
            _separator = string.Empty;
        }
        
        public JsonWriter(StringBuilder builder):this(new StringWriter(builder))
        {
        }

        public JsonWriter()
            : this(new StringBuilder())
        {
        }
        
        public void BeginObject()
        {
            _writer.Write(_separator + "{");
            _separator = string.Empty;
        }
        
        public void BeginObject(string name)
        {
            WriteName(name);
            _writer.Write("{");
            _separator = string.Empty;
        }
        
        public void EndObject()
        {
            _writer.Write("}");
            _separator = ",";
        }
        
        public void BeginArray()
        {
            _writer.Write(_separator + "[");
            _separator = string.Empty;
        }
        
        public void BeginArray(string name)
        {
            WriteName(name);
            _writer.Write("[");
            _separator = string.Empty;
        }
        
        public void WriteArray(params int ? [] elements)
        {
            BeginArray();
            foreach (int? elem in elements)
                WriteValue(elem);
            EndArray();
        }

        public void WriteArray(params string [] elements)
        {
            BeginArray();
            foreach (string elem in elements)
                WriteValue(elem);
            EndArray();
        }

        public void WriteArray(params bool ? [] elements)
        {
            BeginArray();
            foreach (bool ? elem in elements)
                WriteValue(elem);
            EndArray();
        }
        
        public void EndArray()
        {
            _writer.Write("]");
            _separator = ",";
        }
        
        public void BeginCall(string functionName)
        {
            _writer.Write("{0}(", functionName);
            _separator = string.Empty;
        }
        
        public void EndCall()
        {
            _writer.Write(");");
        }
        
        public void WriteCall(string functionName)
        {
            _writer.Write("{0}();", functionName);
        }

        public void WriteCall(string functionName, params string [] args)
        {
            BeginCall(functionName);
            for (int i = 0; i < args.Length; i++)
                WriteValue(args[i]);
            EndCall();
        }

        /// <summary>
        /// Escapes chars in string base on RFC 4627 page 4 http://www.ietf.org/rfc/rfc4627.txt?number=4627
        /// </summary>
        /// <param name="value">string to escape</param>
        private void WriteEscapedString(string value)
        {
            string escaped = EscapeRegex.Replace(value, EscapeMatchEvaluator);
            _writer.Write(@"""{0}""", escaped);
        }
        
        private string EscapeMatchEvaluator(Match match)
        {
            Group group = match.Groups[0];
            switch (group.Value)
            {
                // two-character sequence escape representations
                case "\"": return "\\\""; // "    quotation mark  U+0022
                case "\\": return "\\\\"; // \    reverse solidus U+005C
                case "\b": return "\\b";  // b    backspace       U+0008
                case "\f": return "\\f";  // f    form feed       U+000C
                case "\n": return "\\n";  // n    line feed       U+000A
                case "\r": return "\\r";  // r    carriage return U+000D
                case "\t": return "\\t";  // t    tab             U+0009
                // unicodes                  uXXXX                U+XXXX
                default: return string.Format("\\u{0:X4}", (int)group.Value[0]);
            }
        }
        
        private void WriteName(string name)
        {
            _writer.Write(_separator + "\"{0}\":", name);
            _separator = ",";
        }
        
        private void WriteSeparator()
        {
            if (_separator.Length > 0)
                _writer.Write(_separator);
            _separator = ",";
        }
        
        public void WriteValue(string value)
        {
            WriteSeparator();
            InternalWriteValue(value);
        }
        
        public void WriteValue(string format, params object [] args)
        {
            WriteSeparator();
            InternalWriteValue(string.Format(format, args));
        }
        
        public void WriteValue(Enum value)
        {
            WriteSeparator();
            InternalWriteValue(value);
        }

        public void WriteValue(int ? value)
        {
            WriteSeparator();
            InternalWriteValue(value);
        }

        public void WriteValue(bool? value)
        {
            WriteSeparator();
            InternalWriteValue(value);
        }

        public void WriteProperty(string name, string value)
        {
            WriteName(name);
            InternalWriteValue(value);
        }
        
        public void WriteProperty(string name, string format, params object [] args)
        {
            WriteName(name);
            InternalWriteValue(string.Format(format, args));
        }

        public void WriteProperty(string name, string value, bool nullIfNullOrEmpty)
        {
            WriteName(name);
            if (!nullIfNullOrEmpty && value == null || string.IsNullOrEmpty(value))
                _writer.Write(JsonNull);
            else
                WriteEscapedString(value);
        }

        public void WriteProperty(string name, Enum value)
        {
            WriteName(name);
            InternalWriteValue(value);
        }
        
        public void WriteProperty(string name, bool ? value)
        {
            WriteName(name);
            InternalWriteValue(value);
        }

        public void WriteProperty(string name, int ? value)
        {
            WriteName(name);
            InternalWriteValue(value);
        }

        private void InternalWriteValue(int? value)
        {
            if (value == null)
                _writer.Write(JsonNull);
            else
                _writer.Write(value);
        }

        private void InternalWriteValue(bool? value)
        {
            if (value == null)
                _writer.Write(JsonNull);
            else if (value == true)
                _writer.Write(JsonTrue);
            else
                _writer.Write(JsonFalse);
        }

        private void InternalWriteValue(string value)
        {
            if (value == null)
                _writer.Write(JsonNull);
            else
                WriteEscapedString(value);
        }

        private void InternalWriteValue(Enum value)
        {
            if (value == null)
                _writer.Write(JsonNull);
            else
                _writer.Write(@"""{0}""", value);
        }

        public override string ToString()
        {
            return _writer.ToString();
        }
    }
}
